/*
 *	DjoltTemplate.m
 *
 *	David Janes
 *	Discover Anywhere Mobile
 *	March 2010
 *
 *	Copyright 2010 Discover Anywhere Mobile
 */

#import "DjoltTemplate.h"
#import "DjoltNode.h"
#import "DjoltContext.h"

#import <RegexKit/RegexKit.h>

static NSString* djolt_re = 
	@"	("
	@"		{%"
	@"		\\s*"
	@"		(?P<block>"
	@"			(?P<tag>[_a-z]+)"
	@"			("
	@"				\\s+"
	@"				(?P<bargs>.*?)"
	@"			)?"
	@"		\\s*"
	@"		)"
	@"		%}"
	@"	|"
	@"		{{"
	@"		\\s*"
	@"		(?P<expression>.+?)"
	@"		\\s*"
	@"		}}"
	@"	)";
static RKRegex* djolt_rex = nil;

@interface DjoltTemplate ()
- (void) _template:(NSString*)_template_str;
- (void) _feed_position:(u_long)_position;
- (void) _feed_text_to_position:(u_long)_postion;
- (void) _feed_sof;
- (void) _feed_eof;
- (void) _feed_variable;
- (void) _feed_block;
@end

@implementation DjoltTemplate
- (id) initWithTemplate:(NSString*)_template_str
{
    if (self = [super init]) {
		[self _template:_template_str];
	}

	return	self;
}

- (id) initWithTemplateFile:(NSString*)_template_file
{
    if (self = [super init]) {
		NSError* error = nil;
		NSString* str = [NSString stringWithContentsOfFile:_template_file encoding:NSUTF8StringEncoding error:&error];
		if (error) {
			[[NSException exceptionWithName:@"I/O Error" reason:[error localizedDescription] userInfo:nil] raise];
		}

		[self _template:str];
	}

	return	self;
}

- (NSString*) render:(NSDictionary*)_d
{
	NSMutableString* r = [[[NSMutableString alloc] initWithCapacity:0] autorelease];
	DjoltContext* context = [[DjoltContext alloc] initWithDictionary:_d];

	[self->node render:context into:r];

	[context release];

	return	r;
}

- (void) _template:(NSString*)_template_str
{
	if (djolt_rex == nil) {
		djolt_rex = [[RKRegex 
			regexWithRegexString:djolt_re
			options:RKCompileCaseless|RKCompileMultiline|RKCompileDotAll|RKCompileExtended
		] retain];
	}

	self->template_str = _template_str;

	[self _feed_position:0];
	[self _feed_sof];

	self->djolt_e = [_template_str matchEnumeratorWithRegex:djolt_rex];
	while ([self->djolt_e nextRanges] != NULL) {
		NSRange e_range = [self->djolt_e currentRange];

		[self _feed_text_to_position:e_range.location];
		[self _feed_position:(e_range.location + e_range.length)];

		id g_block = nil;
		[self->djolt_e getCapturesWithReferences:@"${block}", &g_block, nil];

		id g_expression = nil;
		[self->djolt_e getCapturesWithReferences:@"${expression}", &g_expression, nil];

		if (g_block) {
			[self _feed_block];
		} else if (g_expression) {
			[self _feed_variable];
		}
	}

	[self _feed_text_to_position:[self->template_str length]];
	[self _feed_eof];
}

- (void) _feed_position:(u_long)_position
{
	self->position = _position;
}

- (void) _feed_text_to_position:(u_long)_position
{
	NSRange range;
	range.location = self->position;
	range.length = _position - self->position;

	NSString* text = [self->template_str substringWithRange:range];

	[self->node addChild:[[DjoltNodeText alloc] initWithText:text]];
}

- (void) _feed_sof
{
	self->node = [[DjoltNodeRoot alloc] init];
}

- (void) _feed_eof
{
	[self->node assertIsClass:[DjoltNodeRoot class]];
}

- (void) _feed_variable
{
	id g_expression = nil;
	[self->djolt_e getCapturesWithReferences:@"${expression}", &g_expression, nil];

	[self->node addChild:[[DjoltNodeVariable alloc] initWithExpression:g_expression]];
}

- (void) _feed_block
{
	id g_tag = nil;
	[self->djolt_e getCapturesWithReferences:@"${tag}", &g_tag, nil];

	if (self->in_block_comment && [@"endcomment" compare:g_tag]) {
		return;
	}

	[self
		performSelector:NSSelectorFromString([NSString stringWithFormat:@"_feed_block_%@", g_tag])
	];
}

- (void) _feed_block_if
{
	id g_bargs = nil;
	[self->djolt_e getCapturesWithReferences:@"${bargs}", &g_bargs, nil];

	DjoltNode* block = [[DjoltNodeIf alloc] initWithParent:self->node arguments:g_bargs];

	[self->node addChild:block];
	self->node = block;
}

- (void) _feed_block_endif
{
	[self->node assertIsClass:[DjoltNodeIf class]];
	self->node = self->node.parent;
}

- (void) _feed_block_ifequal
{
	id g_bargs = nil;
	[self->djolt_e getCapturesWithReferences:@"${bargs}", &g_bargs, nil];

	DjoltNode* block = [[DjoltNodeIfEqual alloc] initWithParent:self->node arguments:g_bargs inverted:NO];

	[self->node addChild:block];
	self->node = block;
}

- (void) _feed_block_endifequal
{
	[self->node assertIsClass:[DjoltNodeIfEqual class]];
	self->node = self->node.parent;
}

- (void) _feed_block_ifnotequal
{
	id g_bargs = nil;
	[self->djolt_e getCapturesWithReferences:@"${bargs}", &g_bargs, nil];

	DjoltNode* block = [[DjoltNodeIfEqual alloc] initWithParent:self->node arguments:g_bargs inverted:YES];

	[self->node addChild:block];
	self->node = block;
}

- (void) _feed_block_endifnotequal
{
	[self->node assertIsClass:[DjoltNodeIfEqual class]];
	self->node = self->node.parent;
}

- (void) _feed_block_else
{
	DjoltNode* if_node = self->node;

	/*
	 *	Pop old if
	 */
	[self->node assertIsClass:[DjoltNodeInvertable class]];
	self->node = self->node.parent;

	/*
	 *	Push new (inverted) if
	 */
	DjoltNode* block = [(DjoltNodeInvertable*)if_node invert];

	[self->node addChild:block];
	self->node = block;
}


- (void) _feed_block_for
{
	id g_bargs = nil;
	[self->djolt_e getCapturesWithReferences:@"${bargs}", &g_bargs, nil];

	DjoltNode* block = [[DjoltNodeFor alloc] initWithParent:self->node arguments:g_bargs];

	[self->node addChild:block];
	self->node = block;
}

- (void) _feed_block_endfor
{
	[self->node assertIsClass:[DjoltNodeFor class]];
	self->node = self->node.parent;
}

- (void) _feed_block_comment
{
	self->in_block_comment = YES;

	DjoltNode* block = [[DjoltNodeComment alloc] initWithParent:self->node];

	[self->node addChild:block];
	self->node = block;
}

- (void) _feed_block_endcomment
{
	self->in_block_comment = NO;

	[self->node assertIsClass:[DjoltNodeComment class]];
	self->node = self->node.parent;
}
@end
